#include "ReportUtils.hh"
#include "BankSystem.hh"
#include "ReportManager.hh"
#include <iostream>
#include <limits>
#include <sstream>
#include <chrono>
#include <ctime>
#include <iomanip>


namespace mini_bank {

int BankSystem::readInt(const std::string& prompt) {
    int x;
    while (true) {
        std::cout << prompt;
        if (std::cin >> x) break;
        std::cin.clear();
        std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
        std::cout << "Please enter a valid number!\n";
    }
    std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
    return x;
}

double BankSystem::readDouble(const std::string& prompt) {
    double x;
    while (true) {
        std::cout << prompt;
        if (std::cin >> x) break;
        std::cin.clear();
        std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
        std::cout << "Please enter a valid amount!\n";
    }
    std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
    return x;
}

std::string BankSystem::nextTxId() {
    std::ostringstream ss;
    ss << "T" << std::setw(3) << std::setfill('0') << (++txCounter);
    return ss.str();
}

Account* BankSystem::findAccountById(const std::string& id) {
    for (const auto& pair : customers) {
        auto& cust = pair.second;
        if (cust->getSavings()->getId() == id)   return cust->getSavings();
        if (cust->getChecking()->getId() == id)  return cust->getChecking();
    }
    return nullptr;
}

void BankSystem::run() {
    while (true) {
        int opt = readInt(
            "\n===== Banking System =====\n"
            "1. Register new customer\n"
            "2. Access customer account\n"
            "3. List all accounts\n"
            "4. Generate global transaction report\n"
            "5. Exit\n"
            "Enter choice: ");
        switch (opt) {
            case 1: registerCustomer(); break;
            case 2: accessCustomer();   break;
            case 3: listAllAccounts();  break;
            case 4: globalReport();     break;
            case 5: return;
            default: std::cout << "Invalid option\n";
        }
    }
}

void BankSystem::registerCustomer() {
    std::cout << "Enter customer name: ";
    std::string name;
    std::getline(std::cin, name);
    if (customers.count(name)) {
        std::cout << "Customer already exists!\n";
        return;
    }
    auto sid = idGen.generate("21");
    auto cid = idGen.generate("22");

    double sbal = readDouble("Enter initial savings balance: ");
    double cbal = readDouble("Enter initial checking balance: ");
    
    customers[name] = std::make_unique<Customer>(name, sid, cid, sbal, cbal);
    std::cout << "Customer " << name << " registered successfully!\n"
              << "Savings Account: " << sid << " (Balance: ¥" << sbal << ")\n"
              << "Checking Account: " << cid << " (Balance: ¥" << cbal << ")\n";
}

void BankSystem::accessCustomer() {
    std::cout << "Enter customer name: ";
    std::string name;
    std::getline(std::cin, name);
    auto it = customers.find(name);
    if (it == customers.end()) {
        std::cout << "Customer not found.\n";
        return;
    }
    customerMenu(it->second.get());
}

void BankSystem::listAllAccounts() {
    std::cout << "\n--- All Accounts ---\nSavings Accounts:\n";
    for (auto& pair : customers) {
        auto c = pair.second.get(); // Get Customer*
        auto s = c->getSavings();
        std::cout << s->getId()
                  << " | " << c->getName()
                  << " | ¥" << s->getBalance()
                  << "\n";
    }
    std::cout << "Checking Accounts:\n";
    for (auto& pair : customers) {
        auto c = pair.second.get();
        auto k = c->getChecking();
        std::cout << k->getId()
                  << " | " << c->getName()
                  << " | ¥" << k->getBalance()
                  << "\n";
    }
    std::cout << "Total customers: " << customers.size() << "\n";
}

void BankSystem::globalReport() {
    std::vector<Transaction> all;
    for (const auto& pair : customers) {
        auto c = pair.second.get();
        auto t = c->getAllTransactions();
        all.insert(all.end(), t.begin(), t.end());
    }
    
    auto filename = ReportUtils::generateFilename("global");
    try {
        ReportManager::writeTransactionsToFile(all, filename);
        std::cout << "Global report generated: " << filename << "\n";
    } catch (const std::exception& e) {
        std::cout << "Report generation failed: " << e.what() << "\n";
    }
}

void BankSystem::customerMenu(Customer* cust) {
    while (true) {
        int opt = readInt(
            "\n=== Customer: " + cust->getName() + " ===\n"
            "1. Access savings account\n"
            "2. Access checking account\n"
            "3. View all transactions\n"
            "4. Generate customer report\n"
            "5. Return\n"
            "Enter choice: ");
        if (opt == 5) break;
        switch (opt) {
            case 1: accountMenu(cust->getSavings(), cust); break;
            case 2: accountMenu(cust->getChecking(), cust); break;
            case 3: {
                auto txs = cust->getAllTransactions();
                for (auto& t : txs) {
                    std::cout << t.toString() << "\n";
                }
                break;
            }
            case 4: {
                auto txs = cust->getAllTransactions();
                auto filename = ReportUtils::generateFilename("customer", cust->getName());
                try {
                    ReportManager::writeTransactionsToFile(txs, filename);
                    std::cout << "File generated: " << filename << "\n";
                } catch (const std::exception& e) {
                    std::cout << "Failed: " << e.what() << "\n";
                }
                break;
            }
            default: std::cout << "Invalid option\n";
        }
    }
}

void BankSystem::accountMenu(Account* acc, Customer* cust) {
    while (true) {
        std::cout << "\n--- Account: " << acc->getId() << " ---\n"
                  << "Owner: " << acc->getOwnerName() << "\n"
                  << "Balance: ¥" << acc->getBalance() << "\n";

        int opt = readInt(
            "1. Deposit\n"
            "2. Withdraw\n"
            "3. Transfer\n"
            "4. View transaction history\n"
            "5. Generate account report\n"
            "6. Return\n"
            "Enter choice: ");
        if (opt == 6) break;

        double amt; std::string note, dest;
        switch (opt) {
            case 1:
                amt  = readDouble("Deposit amount: ");
                std::cout << "Note: ";
                std::getline(std::cin, note);
                if (acc->deposit(amt, note)) {
                    auto txid = nextTxId();
                    acc->addTransaction(
                        Transaction(txid, "Deposit", amt, note));
                    std::cout << "Deposit successful! ID=" << txid << "\n";
                }
                break;
            case 2:
                amt  = readDouble("Withdrawal amount: ");
                std::cout << "Note: ";
                std::getline(std::cin, note);
                if (acc->withdraw(amt, note)) {
                    auto txid = nextTxId();
                    acc->addTransaction(
                        Transaction(txid, "Withdraw", amt, note));
                    std::cout << "Withdrawal successful! ID=" << txid << "\n";
                }
                break;
            case 3: // Transfer
                std::cout << "Target account ID: ";
                std::getline(std::cin, dest);
                amt  = readDouble("Transfer amount: ");
                std::cout << "Note: ";
                std::getline(std::cin, note);
                {
                    Account* toAcc = findAccountById(dest);
                    if (!toAcc) {
                        std::cout << "Target account not found.\n";
                        break;
                    }
                    if (acc->transfer(*toAcc, amt, note, true)) {
                        std::string txNote = note + " [From:" + acc->getId() + " To:" + toAcc->getId() + "]";
                        auto txid = nextTxId();
                        acc->addTransaction(Transaction(txid, "Transfer", -amt, txNote));
                        toAcc->addTransaction(Transaction(txid, "Transfer", amt, txNote));
                        std::cout << "Transfer successful! ID=" << txid << "\n";
                    }
                }
                break;
            case 4:
                for (auto& t : acc->getTransactions()) {
                    std::cout << t.toString() << "\n";
                }
                break;
                case 5: {
                    auto txs = acc->getTransactions();
                    auto filename = ReportUtils::generateFilename("account", acc->getId());
                    try {
                        ReportManager::writeTransactionsToFile(txs, filename);
                        std::cout << "File generated: " << filename << "\n";
                    } catch (const std::exception& e) {
                        std::cout << "Failed: " << e.what() << "\n";
                    }
                    break;
                }
            default:
                std::cout << "Invalid option\n";
        }
    }
}

} // namespace mini_bank